﻿using AutoMapper;

namespace Tinter.Common.TypeMappers
{
    /// <summary>
    /// The type map default and base configuration with AutoMapper implementation.
    /// With this class you can specify the map for convert a source type into target type.
    /// This maps pec supports before and after injection points.
    /// </summary>
    /// <typeparam name="TSource">The source </typeparam>
    /// <typeparam name="TTarget">The target type</typeparam>
    public class TypeMapSpecBase<TSource, TTarget> : ITypeMapSpec
        where TSource : class
        where TTarget : class
    {
        #region Members

        int? requestedHashCode;

        #endregion

        #region Properties

        /// <summary>
        /// Get descriptor for this instance. 
        /// <remarks>
        /// This descriptor is not unique string.
        /// </remarks>
        /// </summary>
        public string Descriptor
        {
            get { return descriptor ?? (descriptor = GetDescriptor()); }
        }
        private static string descriptor;

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Get Spec descriptor.
        /// </summary>
        /// <returns>Associated descriptor</returns>
        public static string GetDescriptor()
        {
            return string.Format("{0}<->{1}", typeof(TSource).FullName, typeof(TTarget).FullName);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Excuted action before map source to target.
        /// <remarks>
        /// If you use a framework for mappings you can use this method
        /// for preparing or setup the map.
        /// </remarks>
        /// </summary>
        /// <param name="source">The source to adapt</param>
        protected virtual void BeforeMap(ref TSource source)
        {
            Mapper.CreateMap<TSource, TTarget>();
        }

        /// <summary>
        /// Executed action after map.
        /// <remarks>
        /// You can use this method for set more sources into adapted object
        /// </remarks>
        /// </summary>
        /// <param name="target">The item adapted </param>
        /// <param name="moreSources">Nested data to use in post filter</param>
        protected virtual void AfterMap(ref TTarget target, params object[] moreSources)
        {
        }

        /// <summary>
        /// Map a source to a target.
        /// </summary>
        /// <remarks>
        /// If you use a framework, use this method for setup or resolve mapping.
        /// <example>Automapper.Map{TSource,TTarget}</example>
        /// </remarks>
        /// <param name="source">The source to map</param>
        /// <returns>A new instance of <typeparamref name="TTarget"/></returns>
        protected virtual TTarget Map(TSource source)
        {
            return Mapper.Map<TSource, TTarget>(source);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adapts a {TSource} instance into a new instance of type {TTarget}.
        /// <remarks>
        /// This is the template method for translate a source type into a destination type. The template
        /// is BeforeMap -> Map -> AfterMap
        /// </remarks>
        /// </summary>
        /// <param name="source">The source item to adapt</param>
        /// <param name="moreSources">Nested data to use in pipeline</param>
        /// <returns>The target instance</returns>
        public TTarget Resolve(TSource source, params object[] moreSources)
        {
            /*
             * Resolve adapter pipeline
             * pre -> map -> post
             */

            //execute prefilter
            BeforeMap(ref source);

            //map from source to target using conventions or specific things ( for each framework )
            TTarget target = Map(source);

            //execute postfilter
            AfterMap(ref target, moreSources);

            //return adapted object
            return target;
        }

        #endregion

        #region Override Methods

        /// <summary>
        /// <see cref="System.Object.Equals"/>
        /// </summary>
        /// <param name="obj"><see cref="System.Object.Equals"/></param>
        /// <returns><see cref="System.Object.Equals"/></returns>
        public override bool Equals(object obj)
        {
            bool equals = obj != null;

            var spec = obj as TypeMapSpecBase<TSource, TTarget>;

            if (spec != null)
            {
                equals = this.Descriptor == spec.Descriptor;
            }

            return equals;
        }

        public override int GetHashCode()
        {
            if (!requestedHashCode.HasValue)    //Why 31 ( factor for scrambling function)?--> "distributed better over the buckets"
            {
                requestedHashCode = typeof(TSource).GetHashCode() ^ typeof(TTarget).GetHashCode() * 31;
            }

            return requestedHashCode.Value;
        }

        #endregion
    }
}
